// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/ostreams.h"
#include "src/objects.h"
#include "src/objects/string.h"

#if V8_OS_WIN
#include <windows.h>
#if _MSC_VER < 1900
#define snprintf sprintf_s
#endif
#endif

#if defined(ANDROID) && !defined(V8_ANDROID_LOG_STDOUT)
#define LOG_TAG "v8"
#include <android/log.h> // NOLINT
#endif

#include "src/objects-inl.h" // weolar

namespace v8 {
namespace internal {

    DbgStreamBuf::DbgStreamBuf() { setp(data_, data_ + sizeof(data_)); }

    DbgStreamBuf::~DbgStreamBuf() { sync(); }

    int DbgStreamBuf::overflow(int c)
    {
#if V8_OS_WIN
        if (!IsDebuggerPresent()) {
            return 0;
        }

        sync();

        if (c != EOF) {
            if (pbase() == epptr()) {
                auto as_char = static_cast<char>(c);
                OutputDebugStringA(&as_char);
            } else {
                sputc(static_cast<char>(c));
            }
        }
#endif
        return 0;
    }

    int DbgStreamBuf::sync()
    {
#if V8_OS_WIN
        if (!IsDebuggerPresent()) {
            return 0;
        }

        if (pbase() != pptr()) {
            OutputDebugStringA(std::string(pbase(), static_cast<std::string::size_type>(pptr() - pbase()))
                                   .c_str());
            setp(pbase(), epptr());
        }
#endif
        return 0;
    }

    DbgStdoutStream::DbgStdoutStream()
        : std::ostream(&streambuf_)
    {
    }

    OFStreamBase::OFStreamBase(FILE* f)
        : f_(f)
    {
    }

    int OFStreamBase::sync()
    {
        std::fflush(f_);
        return 0;
    }

    OFStreamBase::int_type OFStreamBase::overflow(int_type c)
    {
        return (c != EOF) ? std::fputc(c, f_) : c;
    }

    std::streamsize OFStreamBase::xsputn(const char* s, std::streamsize n)
    {
        return static_cast<std::streamsize>(
            std::fwrite(s, 1, static_cast<size_t>(n), f_));
    }

    OFStream::OFStream(FILE* f)
        : std::ostream(nullptr)
        , buf_(f)
    {
        DCHECK_NOT_NULL(f);
        rdbuf(&buf_);
    }

#if defined(ANDROID) && !defined(V8_ANDROID_LOG_STDOUT)
    AndroidLogStream::~AndroidLogStream()
    {
        // If there is anything left in the line buffer, print it now, even though it
        // was not terminated by a newline.
        if (!line_buffer_.empty()) {
            __android_log_write(ANDROID_LOG_INFO, LOG_TAG, line_buffer_.c_str());
        }
    }

    std::streamsize AndroidLogStream::xsputn(const char* s, std::streamsize n)
    {
        const char* const e = s + n;
        while (s < e) {
            const char* newline = reinterpret_cast<const char*>(memchr(s, '\n', e - s));
            size_t line_chars = (newline ? newline : e) - s;
            line_buffer_.append(s, line_chars);
            // Without terminating newline, keep the characters in the buffer for the
            // next invocation.
            if (!newline)
                break;
            // Otherwise, write out the first line, then continue.
            __android_log_write(ANDROID_LOG_INFO, LOG_TAG, line_buffer_.c_str());
            line_buffer_.clear();
            s = newline + 1;
        }
        return n;
    }
#endif

    namespace {

        // Locale-independent predicates.
        bool IsPrint(uint16_t c) { return 0x20 <= c && c <= 0x7E; }
        bool IsSpace(uint16_t c) { return (0x9 <= c && c <= 0xD) || c == 0x20; }
        bool IsOK(uint16_t c) { return (IsPrint(c) || IsSpace(c)) && c != '\\'; }

        std::ostream& PrintUC16(std::ostream& os, uint16_t c, bool (*pred)(uint16_t))
        {
            char buf[10];
            const char* format = pred(c) ? "%c" : (c <= 0xFF) ? "\\x%02x" : "\\u%04x";
            snprintf(buf, sizeof(buf), format, c);
            return os << buf;
        }

        std::ostream& PrintUC16ForJSON(std::ostream& os, uint16_t c,
            bool (*pred)(uint16_t))
        {
            // JSON does not allow \x99; must use \u0099.
            char buf[10];
            const char* format = pred(c) ? "%c" : "\\u%04x";
            snprintf(buf, sizeof(buf), format, c);
            return os << buf;
        }

        std::ostream& PrintUC32(std::ostream& os, int32_t c, bool (*pred)(uint16_t))
        {
            if (c <= String::kMaxUtf16CodeUnit) {
                return PrintUC16(os, static_cast<uint16_t>(c), pred);
            }
            char buf[13];
            snprintf(buf, sizeof(buf), "\\u{%06x}", c);
            return os << buf;
        }

    } // namespace

    std::ostream& operator<<(std::ostream& os, const AsReversiblyEscapedUC16& c)
    {
        return PrintUC16(os, c.value, IsOK);
    }

    std::ostream& operator<<(std::ostream& os, const AsEscapedUC16ForJSON& c)
    {
        if (c.value == '\n')
            return os << "\\n";
        if (c.value == '\r')
            return os << "\\r";
        if (c.value == '\t')
            return os << "\\t";
        if (c.value == '\"')
            return os << "\\\"";
        return PrintUC16ForJSON(os, c.value, IsOK);
    }

    std::ostream& operator<<(std::ostream& os, const AsUC16& c)
    {
        return PrintUC16(os, c.value, IsPrint);
    }

    std::ostream& operator<<(std::ostream& os, const AsUC32& c)
    {
        return PrintUC32(os, c.value, IsPrint);
    }

    std::ostream& operator<<(std::ostream& os, const AsHex& hex)
    {
        // Each byte uses up to two characters. Plus two characters for the prefix,
        // plus null terminator.
        DCHECK_GE(sizeof(hex.value) * 2, hex.min_width);
        static constexpr size_t kMaxHexLength = 3 + sizeof(hex.value) * 2;
        char buf[kMaxHexLength];
        snprintf(buf, kMaxHexLength, "%s%.*" PRIx64, hex.with_prefix ? "0x" : "",
            hex.min_width, hex.value);
        return os << buf;
    }

    std::ostream& operator<<(std::ostream& os, const AsHexBytes& hex)
    {
        uint8_t bytes = hex.min_bytes;
        while (bytes < sizeof(hex.value) && (hex.value >> (bytes * 8) != 0))
            ++bytes;
        for (uint8_t b = 0; b < bytes; ++b) {
            if (b)
                os << " ";
            uint8_t printed_byte = hex.byte_order == AsHexBytes::kLittleEndian ? b : bytes - b - 1;
            os << AsHex((hex.value >> (8 * printed_byte)) & 0xFF, 2);
        }
        return os;
    }

} // namespace internal
} // namespace v8

#undef snprintf
#undef LOG_TAG
